package com.csailing4cloud.csailing.common.redis.component;

import com.csailing4cloud.csailing.common.core.util.MathUtil;
import com.csailing4cloud.csailing.common.redis.annotation.RateLimiterDetail;
import com.csailing4cloud.csailing.common.redis.constants.RedisExcuteScriptConstant;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author dyh
 * 限流器
 */
public class RedisRateLimiter {

    @Autowired
    @Qualifier("rateRedisTemplate")
    private RedisTemplate<String,Long> rateRedisTemplate;


    /**
     * 获得
     * @param keyPrefix
     * @param rateLimiterDetail
     * @return
     */
    public boolean acquire(String keyPrefix, RateLimiterDetail rateLimiterDetail){
        RedisScript<Long> redisScript = new DefaultRedisScript<>(RedisExcuteScriptConstant.LUA_SECOND_SCRIPT, Long.class);
        List<String> keys = new ArrayList<>();
        String keyName = getKeyName(keyPrefix);
        keys.add(keyName);
        Long execute = rateRedisTemplate.execute(redisScript, keys,getExpire(rateLimiterDetail), rateLimiterDetail.permits());
        return execute>0;
    }

    private String getKeyName(String keyPrefix) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(RedisKeyEnum.RATE_LIMITER.getRedisKey(keyPrefix));
        return stringBuffer.toString();
    }

    private Long getExpire(RateLimiterDetail rateLimiterDetail){
        TimeUnit timeUnit = rateLimiterDetail.timeUnit();
        long time = rateLimiterDetail.time();
        Long expire = 0L;
        if (timeUnit == TimeUnit.SECONDS) {
            expire = time;
        } else if (timeUnit == TimeUnit.MINUTES) {
            expire = Long.valueOf(MathUtil.mul(String.valueOf(time),String.valueOf(60)));
        } else if (timeUnit == TimeUnit.HOURS) {
            expire = Long.valueOf(MathUtil.mul(String.valueOf(time),String.valueOf(3600)));
        } else if (timeUnit == TimeUnit.DAYS) {
            expire = Long.valueOf(MathUtil.mul(String.valueOf(time), String.valueOf(24*3600)));
        } else {
            throw new IllegalArgumentException("Don't support this TimeUnit: " + timeUnit);
        }
        return expire;
    }

    @Getter
    enum RedisKeyEnum{
        RATE_LIMITER("rate:limiter:%s",60,TimeUnit.SECONDS);
        private String key;
        private long timeout;
        private TimeUnit timeUnit;

        RedisKeyEnum(String key, int timeout,TimeUnit timeUnit) {
            this.key = key;
            this.timeout = timeout;
            this.timeUnit = timeUnit;
        }

        public String  getRedisKey(Object... args) {
            return String.format(this.getKey(),args);
        }
    }
}
